--[[
Config Starts here
-]]
Server = "irc.rmrk.net" -- Where do you want to connect
Port = 6667 -- which port
Chan = "#thc" --Channel the bot should lurk in
Nicservpass = "sudafed" --nickserv pass Please register the nickname by yourself i refuse to automate this process.
Nick = "THC_TheHole"--The Nickname you want to give the bot eg. ahrefBot
User = "THC_TheHole beans beans2:Not a bot!" --I never got the hang of this just enter something along the lines of what i have
--[[
Config Ends here
#EDIT BELOW THIS LINE AT YOUR OWN PERIL!
-]]

--Variable Initilization
Ops = {} --Stores people with ops in the channel(UNTESTED)
irc = {} --Irc "Class"
irc.ircCommands = {} --Stores the in irc commands
irc.rawCommands = {} --Stores raw irc commands. I hope to speed up the parser :P
Players = 0 --Holds number of players(faster then checking them when the player joins)
Host = GetConVarString("hostname") --Hostname of the server for later use
Map = string.Replace(game.GetMap(),".bsp","")--map name of the server minus .bsp

--NOTE: Hooks are placed at the bottom so i can singleplayer ignore them at will. THC members complained holebot activated in singleplayer and well he did
require("socket") --require the socket module.

function irc.ircCommands.add(cmd,func) -- this was simpler than some guys code so oh well :D
	irc.ircCommands[cmd] = func
end
function irc.rawCommands.add(cmd,func)
	irc.rawCommands[cmd] = func
end
--connect to the server and actually do something. YAY FINALLY
function irc.start()
	irc.ircCommands.add("!players",irc.players)
	irc.ircCommands.add("!exec",irc.exec)
	irc.ircCommands.add("!raw",irc.raw)
	irc.ircCommands.add("!kick",irc.kick)
	irc.ircCommands.add("!ban",irc.ban)
	irc.ircCommands.add("!count",irc.count)
	irc.ircCommands.add("!debug",irc.debug)
	irc.rawCommands.add("PRIVMSG",irc.privMsg)
	irc.rawCommands.add("JOIN",irc.join)
	irc.rawCommands.add("PART",irc.part)
	irc.rawCommands.add("QUIT",irc.part)
	irc.rawCommands.add("TOPIC",irc.topic)
	irc.rawCommands.add("319",irc.threeonenine)
	if s then
		s:close()
	end
	s = socket.connect(Server, Port)
	s:settimeout(0)
	s:setoption("keepalive",true)
	irc.send("NICK "..Nick)
	irc.send("USER "..User)
	timer.Simple(1,function() --Let all the authentication shit happen
		irc.send("PRIVMSG nickserv :IDENTIFY "..Nicservpass)	
		irc.send("JOIN "..Chan) 
	end)  
	timer.Simple(2,function() --chanserv is slow
		irc.send("PRIVMSG "..Chan.." :Gmod Server Bot 0.5 By ahref") 
		irc.topic()
	end)
	
end

--Sends a raw line to the socket. Avoids me having to do \r\n everywhere
function irc.send(txt)
	if not s then return end --dont do anything if theres no socket
	if not s:send(txt.."\r\n") then --The send line yay
		ServerLog("ERROR Sending: "..txt)
	end
end

--sends a message to Chan I won't allow messages outside the bots controlled channel. 
function irc.msg(txt)
	irc.send("PRIVMSG "..Chan.." :"..txt)--helps alot
end

--well in an ideal world this would run on a force restart but it doesnt. However during map changes and controlled restarts it does :D
function irc.shut()
    irc.send("QUIT Alas I Have Crashed!")
	s:close()--Leaving this shit open breaks the next restart cycle. Again, IT WONT CLOSE AFTER A CRASH D:
end

function irc.topic(prefix,cmd,param,u)
	if u == Nick then return false end
	local changedTopic = string.match(param, ":(.*)") or nil
	if changedTopic then
		local proposedTopic = Chan.." |"..Host.." |Players: "..Players.."|Map: "..Map --i could put this on the next line but it would 		look 	less apealing
		irc.send("TOPIC "..Chan.." :"..proposedTopic)
		return true
		 
	else
		return false
	end
end

--this function keeps the bot alive and feeds the recieved lines to my messy parser :D
function irc.think()
	if not s then--oh dear we didnt start lets try again
		irc.start()
		return
	end
	local parse = s:receive('*l')--recieve the last line
	if parse then
		if string.sub( parse, 1, 4 ) == "PING" then--Highest priority or the bot disconnects :D
			irc.send(string.gsub( parse, "PING", "PONG" ))
			return
		end
		irc.parse(parse)
		return
	end
end

--This constantly needs work ok. see my huge pattern and make it work for large ammounts of cake :D
function irc.parse(p)
	if string.find(p,"ERROR") then 
		irc.msg("Error report(Please Tell ahref):"..p)
		return false --cant do anything with an error.
	end
	prefix, cmd, param = string.match( p, "^:([^ ]+) ([^ ]+)(.*)$" )--found this one online helpful but doesnt give it to me straight
	if prefix and string.find( prefix,"!" ) then--we can only grab a user if the message actually came from a user.
		user = string.match( prefix, "(%w*)!" )
		--if user == "IRC" or not user then return true end --Usually server based messages. I really couldnt care.
	end
	local a,err = pcall(irc.rawCommands[cmd],prefix,cmd,param,user)
	if not a then
		if err ~= "attempt to call a nil value" then
			irc.msg("heres some lua give it to ahref: "..err)
			return false
		end
	end
end

function irc.playerConnect(ply)
	local nick = ply:Nick()
	irc.msg(nick..":"..ply:EntIndex().." has connected.")
	Players = Players + 1
	irc.topic()
end

function irc.playerDisconnect(ply)
	local nick = ply:Nick()
	irc.msg(nick..":"..ply:EntIndex().." has disconnected.")
	Players = Players - 1
	irc.topic()
end

function irc.gameMsg(u,txt,toall)
	if string.sub(txt,2,7) ~= "report" and string.sub(txt,1,1) == "!" then
			return 
	end
	local nick = u:Nick()
	if  string.sub(txt,1,1) ~= "%" then
		irc.msg("<"..nick..">:"..txt)
		return 
	else
		--PLACEHOLDER
	end
	
end

function irc.privMsg(prefix,cmd,param,u)
	local channel = string.match(param, "#[%w]+") 
	if channel ~= Chan then return end --stfu 
	local message = string.match(param, " :([%W%w%s]+)") 
	if string.sub(message,1,1) == "%" then
		for k,v in pairs(Ops) do irc.msg("Key "..k.." Value: "..tostring(v)) end
	end
	if  string.sub(message,1,1) ~= "!" then
		if string.find(message,"ACTION") then
			string.gsub(message,"ACTION","")
			game.ConsoleCommand("say ".."<irc>"..u..message.." \n")
			return true
		else
			game.ConsoleCommand("say ".."<irc>".."<"..u..">"..message.." \n")
			return true
		end
	else
	--string.gsub(oldString,"test","stickup")
		local line = string.Explode(" ",message)
		local cmd = line[1]
		table.remove( line, 1 )
		if not cmd then return end
		if not line then return end
		local a,err = pcall(irc.ircCommands[cmd],u,cmd,line)
		if not a then
			if err == "attempt to call a nil value" then
				irc.msg("Unknown Command")
			else
				irc.msg("heres some lua give it to ahref: "..err)
				return false
			end
		end
	end
end

function irc.join(prefix,cmd,param,user)
	irc.send("WHOIS "..user.." "..user)
	return
end

function irc.part(prefix,cmd,param,user)
	if table.HasValue( Ops, user) then
		for k, v in pairs(Ops) do
			if v == user then
				table.remove(Ops,k)
				return
			end
		end
	end
end
function irc.threeonenine(prefix,cmd,param,u)
	local nick = string.match(param,"[%w] ([%w]+)")
	local modes = string.match(param, "([+%%~&@])"..Chan)
	if modes and not modes ~= "+" then 
		if not table.HasValue( Ops, nick ) then
			table.insert( Ops,nick )
		end
	end
end

function irc.players(u,cmd,line)
	num = 0
	players_t = {}
	for k, v in pairs(player.GetAll()) do
		table.insert(players_t,v:Nick()..":"..v:EntIndex())
		num = num + 1
	end
	--string.Implode( String seperator, Table table )
	players = string.Implode(",",players_t)
	irc.msg("Players:"..players.." Total:"..num)
	return
end

function irc.exec(u,cmd,line)
	command = string.Implode(" ",line)
	game.ConsoleCommand( command.."\n" )
	irc.msg("I can't actually tell you if that did anything :D")
	return
end

function irc.raw(u,cmd,line)
	command = string.Implode(" ",line)
	irc.send(command)
	return
end

function irc.debug(u,cmd,line)
	if u ~= "ahref" then
		irc.msg("This is an ahref only command due to its nature PLEASE GTFO IT")
	else
		command = string.Implode(" ",line)
		irc.msg("Running Lua. Result: "..RunString( command ) )
	end
end

function irc.kick(u,cmd,line)
	local p = player.GetByID(tonumber(line[1])) or nil
	if not p then
		irc.msg("Player Does not exist. Do !players to check.")
		return
	end
	table.remove(line,1)
	reason = string.Implode(" ",line)
	p:Kick(reason)
	return
end

function irc.ban(u,cmd,line)
	p = player.GetByID(tonumber(line[1])) or nil
	if not p then
		irc.msg("Player Does not exist. Do !players to check.")
		return
	end
	table.remove(line,1)
	reason = string.Implode(" ",line)
	p:Ban(0,reason)
	return
end

function irc.count(u,cmd,line)
--Entity:EntIndex( ) works on players
	if #line < 2 and string.lower(line[1]) ~="all" then
		irc.msg("syntax: !count id type")
		return
	end
	if line[1] == "all" then
		props = ents.FindByClass("prop_physics")
		irc.msg("Server contains"..#props.." props.")
		return
	end
	count = player.GetByID(tonumber(line[1])) or nil
	search = string.lower(line[2]) or nil
	if not count then
		irc.msg("Player Does not exist. Do !players to check.")
		return
	end
	if search ~= "props" and search ~= "sents" then
		irc.msg("syntax: !count id type(props or sents)")
		return
	end
	irc.msg(count:Nick().." has "..count:GetCount( search ).." "..search..".")
	return
end

if not SinglePlayer() then 
	hook.Add( "Initialize", "ahrefbotstart", irc.start )
	hook.Add( "PlayerInitialSpawn", "ahrefircinitialspawn", irc.playerConnect )
	hook.Add( "PlayerDisconnected", "ahrefircdisconnected", irc.playerDisconnect )
	hook.Add( "PlayerSay", "ahrefircgamemessage", irc.gameMsg )
	hook.Add("Think", "ahrefircthinkloop", irc.think)
	hook.Add( "ShutDown", "ahrefbotshutdown", irc.shut )
	irc.start()
end
--[[:jlj1!~jlj1@FDC777BF.FFB88ACD.E46257AB.IP PRIVMSG #THC :cowbot
[06:20:16] (cowbot@PSIGenix) > :ahref!~ahref@PSI-DA0C356.cable.ubr07.gill.blueyonder.co.uk PART #thc :BEEP
[06:20:21] (cowbot@PSIGenix) > :ahref!~ahref@PSI-DA0C356.cable.ubr07.gill.blueyonder.co.uk JOIN :#thc
--user,cmd,channel,message = string.match(line, "^:([^ ]+)! ([^ ]+) (#[%w%]+) :([%W%w%s]+)$") this does not work and makes me cry :(]]